﻿using System;
#if STANDARD
using Mathf = System.MathF;
#endif
#if GODOT
using Godot;
#endif

namespace Common {
	public static class Easing {
		#if STANDARD
		const float Pi = Mathf.PI;
		#endif
		#if GODOT
		const float Pi = Mathf.Pi;
		#endif

		public delegate float Delegate(float time,float duration=1,float from=0,float to=1);

		public static float Linear(float time, float duration = 1, float from = 0, float to = 1) => LinearChange(time, duration, from, (to - from));
		public static float ExpoIn(float time, float duration = 1, float from = 0, float to = 1) => ExpoInChange(time, duration, from, (to - from));
		public static float ExpoOut(float time, float duration = 1, float from = 0, float to = 1) => ExpoOutChange(time, duration, from, (to - from));
		public static float ExpoInOut(float time, float duration = 1, float from = 0, float to = 1) => ExpoInOutChange(time, duration, from, (to - from));
		public static float ExpoOutIn(float time, float duration = 1, float from = 0, float to = 1) => ExpoOutInChange(time, duration, from, (to - from));
		public static float CircIn(float time, float duration = 1, float from = 0, float to = 1) => CircInChange(time, duration, from, (to - from));
		public static float CircOut(float time, float duration = 1, float from = 0, float to = 1) => CircOutChange(time, duration, from, (to - from));
		public static float CircInOut(float time, float duration = 1, float from = 0, float to = 1) => CircInOutChange(time, duration, from, (to - from));
		public static float CircOutIn(float time, float duration = 1, float from = 0, float to = 1) => CircOutInChange(time, duration, from, (to - from));
		public static float QuadIn(float time, float duration = 1, float from = 0, float to = 1) => QuadInChange(time, duration, from, (to - from));
		public static float QuadOut(float time, float duration = 1, float from = 0, float to = 1) => QuadOutChange(time, duration, from, (to - from));
		public static float QuadInOut(float time, float duration = 1, float from = 0, float to = 1) => QuadInOutChange(time, duration, from, (to - from));
		public static float QuadOutIn(float time, float duration = 1, float from = 0, float to = 1) => QuadOutInChange(time, duration, from, (to - from));
		public static float SineIn(float time, float duration = 1, float from = 0, float to = 1) => SineInChange(time, duration, from, (to - from));
		public static float SineOut(float time, float duration = 1, float from = 0, float to = 1) => SineOutChange(time, duration, from, (to - from));
		public static float SineInOut(float time, float duration = 1, float from = 0, float to = 1) => SineInOutChange(time, duration, from, (to - from));
		public static float SineOutIn(float time, float duration = 1, float from = 0, float to = 1) => SineOutInChange(time, duration, from, (to - from));
		public static float CubicIn(float time, float duration = 1, float from = 0, float to = 1) => CubicInChange(time, duration, from, (to - from));
		public static float CubicOut(float time, float duration = 1, float from = 0, float to = 1) => CubicOutChange(time, duration, from, (to - from));
		public static float CubicInOut(float time, float duration = 1, float from = 0, float to = 1) => CubicInOutChange(time, duration, from, (to - from));
		public static float CubicOutIn(float time, float duration = 1, float from = 0, float to = 1) => CubicOutInChange(time, duration, from, (to - from));
		public static float QuartIn(float time, float duration = 1, float from = 0, float to = 1) => QuartInChange(time, duration, from, (to - from));
		public static float QuartOut(float time, float duration = 1, float from = 0, float to = 1) => QuartOutChange(time, duration, from, (to - from));
		public static float QuartInOut(float time, float duration = 1, float from = 0, float to = 1) => QuartInOutChange(time, duration, from, (to - from));
		public static float QuartOutIn(float time, float duration = 1, float from = 0, float to = 1) => QuartOutInChange(time, duration, from, (to - from));
		public static float QuintIn(float time, float duration = 1, float from = 0, float to = 1) => QuintInChange(time, duration, from, (to - from));
		public static float QuintOut(float time, float duration = 1, float from = 0, float to = 1) => QuintOutChange(time, duration, from, (to - from));
		public static float QuintInOut(float time, float duration = 1, float from = 0, float to = 1) => QuintInOutChange(time, duration, from, (to - from));
		public static float QuintOutIn(float time, float duration = 1, float from = 0, float to = 1) => QuintOutInChange(time, duration, from, (to - from));
		public static float ElasticIn(float time, float duration = 1, float from = 0, float to = 1) => ElasticInChange(time, duration, from, (to - from));
		public static float ElasticOut(float time, float duration = 1, float from = 0, float to = 1) => ElasticOutChange(time, duration, from, (to - from));
		public static float ElasticInOut(float time, float duration = 1, float from = 0, float to = 1) => ElasticInOutChange(time, duration, from, (to - from));
		public static float ElasticOutIn(float time, float duration = 1, float from = 0, float to = 1) => ElasticOutInChange(time, duration, from, (to - from));
		public static float BounceIn(float time, float duration = 1, float from = 0, float to = 1) => BounceInChange(time, duration, from, (to - from));
		public static float BounceOut(float time, float duration = 1, float from = 0, float to = 1) => BounceOutChange(time, duration, from, (to - from));
		public static float BounceInOut(float time, float duration = 1, float from = 0, float to = 1) => BounceInOutChange(time, duration, from, (to - from));
		public static float BounceOutIn(float time, float duration = 1, float from = 0, float to = 1) => BounceOutInChange(time, duration, from, (to - from));
		public static float BackIn(float time, float duration = 1, float from = 0, float to = 1) => BackInChange(time, duration, from, (to - from));
		public static float BackOut(float time, float duration = 1, float from = 0, float to = 1) => BackOutChange(time, duration, from, (to - from));
		public static float BackInOut(float time, float duration = 1, float from = 0, float to = 1) => BackInOutChange(time, duration, from, (to - from));
		public static float BackOutIn(float time, float duration = 1, float from = 0, float to = 1) => BackOutInChange(time, duration, from, (to - from));


		#region Internal
		static float LinearChange(float time, float duration, float start, float change) => change * time / duration + start;

		static float ExpoOutChange(float time, float duration, float start, float change) =>(time == duration) ? start + change : change * (-Mathf.Pow(2, -10 * time / duration) + 1) + start;
		static float ExpoInChange(float time, float duration, float start, float change) => (time == 0) ? start : change * Mathf.Pow(2, 10 * (time / duration - 1)) + start;
		static float ExpoInOutChange(float time, float duration, float start, float change) {
			if (time == 0)
				return start;

			if (time == duration)
				return start + change;

			if ((time /= duration / 2) < 1)
				return change / 2 * Mathf.Pow(2, 10 * (time - 1)) + start;

			return change / 2 * (-Mathf.Pow(2, -10 * --time) + 2) + start;
		}
		static float ExpoOutInChange(float time, float duration, float start, float change) => time < duration / 2 ? ExpoOutChange(time * 2, start, change / 2, duration) : ExpoInChange((time * 2) - duration, start + change / 2, change / 2, duration);
		
		static float CircOutChange(float time, float duration, float start, float change) => change * Mathf.Sqrt(1 - (time = time / duration - 1) * time) + start;
		static float CircInChange(float time, float duration, float start, float change) => -change * (Mathf.Sqrt(1 - (time /= duration) * time) - 1) + start;
		static float CircInOutChange(float time, float duration, float start, float change) => (time /= duration / 2) < 1 ? -change / 2 * (Mathf.Sqrt(1 - time * time) - 1) + start : change / 2 * (Mathf.Sqrt(1 - (time -= 2) * time) + 1) + start;
		static float CircOutInChange(float time, float duration, float start, float change) => time < duration / 2 ? CircOutChange(time * 2, start, change / 2, duration) : CircInChange((time * 2) - duration, start + change / 2, change / 2, duration);

		static float QuadOutChange(float time, float duration, float start, float change) => -change * (time /= duration) * (time - 2) + start;
		static float QuadInChange(float time, float duration, float start, float change) => change * (time /= duration) * time + start;
		static float QuadInOutChange(float time, float duration, float start, float change) => (time /= duration / 2) < 1 ? change / 2 * time * time + start : -change / 2 * ((--time) * (time - 2) - 1) + start;
		static float QuadOutInChange(float time, float duration, float start, float change) => time < duration / 2 ? QuadOutChange(time * 2, start, change / 2, duration) : QuadInChange((time * 2) - duration, start + change / 2, change / 2, duration);

		static float SineOutChange(float time, float duration, float start, float change) => change * Mathf.Sin(time / duration * (Pi / 2)) + start;
		static float SineInChange(float time, float duration, float start, float change) => -change * Mathf.Cos(time / duration * (Pi / 2)) + change + start;
		static float SineInOutChange(float time, float duration, float start, float change) =>(time /= duration / 2) < 1 ? change / 2 * (Mathf.Sin(Pi * time / 2)) + start : -change / 2 * (Mathf.Cos(Pi * --time / 2) - 2) + start;
		static float SineOutInChange(float time, float duration, float start, float change) =>time < duration / 2 ?SineOutChange(time * 2, start, change / 2, duration):SineInChange((time * 2) - duration, start + change / 2, change / 2, duration);

		static float CubicOutChange(float time, float duration, float start, float change) => change * ((time = time / duration - 1) * time * time + 1) + start;
		static float CubicInChange(float time, float duration, float start, float change) => change * (time /= duration) * time * time + start;
		static float CubicInOutChange(float time, float duration, float start, float change) =>(time /= duration / 2) < 1 ?change / 2 * time * time * time + start:change / 2 * ((time -= 2) * time * time + 2) + start;
		static float CubicOutInChange(float time, float duration, float start, float change) =>time < duration / 2?CubicOutChange(time * 2, start, change / 2, duration):CubicInChange((time * 2) - duration, start + change / 2, change / 2, duration);

		static float QuartOutChange(float time, float duration, float start, float change) => -change * ((time = time / duration - 1) * time * time * time - 1) + start;
		static float QuartInChange(float time, float duration, float start, float change) => change * (time /= duration) * time * time * time + start;
		static float QuartInOutChange(float time, float duration, float start, float change) =>(time /= duration / 2) < 1?change / 2 * time * time * time * time + start:-change / 2 * ((time -= 2) * time * time * time - 2) + start;
		static float QuartOutInChange(float time, float duration, float start, float change) =>time < duration / 2?QuartOutChange(time * 2, start, change / 2, duration):QuartInChange((time * 2) - duration, start + change / 2, change / 2, duration);
		
		static float QuintOutChange(float time, float duration, float start, float change) => change * ((time = time / duration - 1) * time * time * time * time + 1) + start;
		static float QuintInChange(float time, float duration, float start, float change) => change * (time /= duration) * time * time * time * time + start;
		static float QuintInOutChange(float time, float duration, float start, float change) => (time /= duration / 2) < 1 ? change / 2 * time * time * time * time * time + start : change / 2 * ((time -= 2) * time * time * time * time + 2) + start;
		static float QuintOutInChange(float time, float duration, float start, float change) => time < duration / 2 ? QuintOutChange(time * 2, start, change / 2, duration) : QuintInChange((time * 2) - duration, start + change / 2, change / 2, duration);
		
		static float ElasticOutChange(float time, float duration, float start, float change) {
			if ((time /= duration) == 1)
				return start + change;

			float p = duration * 0.3f;
			float s = p / 4;

			return (change * Mathf.Pow(2, -10 * time) * Mathf.Sin((time * duration - s) * (2 * Pi) / p) + change + start);
		}
		static float ElasticInChange(float time, float duration, float start, float change) {
			if ((time /= duration) == 1)
				return start + change;

			float p = duration * 0.3f;
			float s = p / 4;

			return -(change * Mathf.Pow(2, 10 * (time -= 1)) * Mathf.Sin((time * duration - s) * (2 * Pi) / p)) + start;
		}
		static float ElasticInOutChange(float time, float duration, float start, float change) {
			if ((time /= duration / 2) == 2)
				return start + change;

			float p = duration * (0.3f * 1.5f);
			float s = p / 4;

			if (time < 1)
				return -0.5f * (change * Mathf.Pow(2, 10 * (time -= 1)) * Mathf.Sin((time * duration - s) * (2 * Pi) / p)) + start;
			return change * Mathf.Pow(2, -10 * (time -= 1)) * Mathf.Sin((time * duration - s) * (2 * Pi) / p) * 0.5f + change + start;
		}
		static float ElasticOutInChange(float time, float duration, float start, float change) => (time < duration / 2) ? ElasticOutChange(time * 2, start, change / 2, duration) : ElasticInChange((time * 2) - duration, start + change / 2, change / 2, duration);

		static float BounceOutChange(float time, float duration, float start, float change) {
			if ((time /= duration) < (1 / 2.75))
				return change * (7.5625f * time * time) + start;
			else if (time < (2 / 2.75))
				return change * (7.5625f * (time -= (1.5f / 2.75f)) * time + 0.75f) + start;
			else if (time < (2.5 / 2.75))
				return change * (7.5625f * (time -= (2.25f / 2.75f)) * time + 0.9375f) + start;
			else
				return change * (7.5625f * (time -= (2.625f / 2.75f)) * time + 0.984375f) + start;
		}
		static float BounceInChange(float time, float duration, float start, float change) => change - BounceOutChange(duration - time, 0, change, duration) + start;
		static float BounceInOutChange(float time, float duration, float start, float change) => time < duration / 2 ? BounceInChange(time * 2, 0, change, duration) * 0.5f + start : BounceOutChange(time * 2 - duration, 0, change, duration) * 0.5f + change * 0.5f + start;
		static float BounceOutInChange(float time, float duration, float start, float change) => time < duration / 2 ? BounceOutChange(time * 2, start, change / 2, duration) : BounceInChange((time * 2) - duration, start + change / 2, change / 2, duration);

		static float BackOutChange(float time, float duration, float start, float change) => change * ((time = time / duration - 1) * time * ((1.70158f + 1) * time + 1.70158f) + 1) + start;
		static float BackInChange(float time, float duration, float start, float change) => change * (time /= duration) * time * ((1.70158f + 1) * time - 1.70158f) + start;
		static float BackInOutChange(float time, float duration, float start, float change) {
			float s = 1.70158f;
			return (time /= duration / 2) < 1 ? change / 2 * (time * time * (((s *= (1.525f)) + 1) * time - s)) + start : change / 2 * ((time -= 2) * time * (((s *= (1.525f)) + 1) * time + s) + 2) + start;
		}
		static float BackOutInChange(float time, float duration, float start, float change) =>time < duration / 2 ? BackOutChange(time * 2, start, change / 2, duration) : BackInChange((time * 2) - duration, start + change / 2, change / 2, duration);
#endregion
	}
}